import { FreeCamera } from '@babylonjs/core/Cameras/freeCamera';
import { WebXRCamera } from '@babylonjs/core/XR/webXRCamera';
import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera';
import { TargetCamera } from '@babylonjs/core/Cameras/targetCamera';
import { Quaternion, Vector3 } from '@babylonjs/core/Maths/math.vector';
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
import { UniversalCamera } from '@babylonjs/core/Cameras/universalCamera';
import { Scene } from '@babylonjs/core/scene';
import { Observer } from '@babylonjs/core/Misc/observable';
import { Helper } from '../tool/helper';


/**
 * 回调函数对象id
 */
let id = 0;

/**
 * 回调函数
 * @param camera 目标相机
 * @param position 位置
 * @param rotation 旋转
 */
interface IHandleCallback {
    (camera: TargetCamera, absolutePosition: Vector3, absoluteRotation: Vector3 | Quaternion, position: Vector3, rotation: Vector3 | Quaternion): void;
}


/**
 * 冻结相机
 * @param cameraOrScene 目标相机或场景
 * @param target 参考对象（变换信息来源）
 */
class FreezeCameraHandleInfo {

    private _handle: { (): void } = () => { };
    private _callback?: IHandleCallback;
    private _activeCameraChangeHandle?: { (): void };
    private _position: Vector3;
    private _rotation: Vector3 | Quaternion;
    private _tempPosition = new Vector3();
    private _target?: TransformNode;
    private _cameraGravity: boolean | null = null;
    private _camera!: TargetCamera;
    private _scene!: Scene;
    private _observer!: Observer<any>;
    private _pCamera!: TargetCamera;

    /**
     * id
     */
    public readonly id: number;

    constructor(cameraOrScene: TargetCamera | Scene, target?: TransformNode) {
        if (cameraOrScene instanceof Scene) {
            this._scene = cameraOrScene;
        } else {
            this._camera = cameraOrScene;
        }
        this.id = id++;
        this._target = target;
        this._position = new Vector3().copyFrom(this.camera.position);
        if (this.camera.rotationQuaternion) {
            this._rotation = new Quaternion().copyFrom(this.camera.rotationQuaternion);
        } else {
            this._rotation = new Vector3().copyFrom(this.camera.rotation);
        }
        this._pCamera = this.camera;

        if (cameraOrScene instanceof Scene) {
            this._activeCameraChangeHandle = () => {
                if (!Helper.isCameraGeneral(this.scene.activeCamera)) return;
                if (this._pCamera.uniqueId === this.scene.activeCamera?.uniqueId) return;

                if (this._cameraGravity !== null) {
                    (this._pCamera as FreeCamera).applyGravity = this._cameraGravity;
                }
                this.disable();
                this.enable();
                this._pCamera = this.camera;
            };
            this.scene.onActiveCameraChanged.add(this._activeCameraChangeHandle);
        }
    }

    /**
     * 获取绝对位置
     */
    public get absolutePosition(): Vector3 {
        if (this._target) {
            return this._target.absolutePosition;
        }
        return this._position;
    }

    /**
     * 获取绝对旋转
     */
    public get absoluteRotation(): Vector3 | Quaternion {
        if (this._target) {
            return this._target.absoluteRotationQuaternion;
        }
        return this._rotation;
    }

    /**
     * 获取位置
     */
    public get position(): Vector3 {
        if (this._target) {
            return this._target.position;
        }
        return this._position;
    }

    /**
     * 获取旋转
     */
    public get rotation(): Vector3 | Quaternion {
        if (this._target) {
            return this._target.rotationQuaternion || this._target.rotation;
        }
        return this._rotation;
    }

    /**
     * 临时位置变量
     */
    public get tempPosition(): Vector3 {
        return this._tempPosition;
    }

    /**
     * 设置冻结行为发生时的回调函数
     */
    public set callback(func: IHandleCallback) {
        this._callback = func;
        this._handle = () => {
            if (this._callback) this._callback(this.camera, this.absolutePosition, this.absoluteRotation, this.position, this.rotation);
        };
    }

    /**
     * 启用冻结行为
     */
    public enable(): void {
        if ((this._cameraGravity === null) && this.isFPSCamera()) {
            this._cameraGravity = (this.camera as FreeCamera).applyGravity;
        }
        if (this.isFPSCamera()) {
            (this.camera as FreeCamera).applyGravity = false;
        }
        if (this.camera instanceof WebXRCamera) {
            (this.camera as any)._deferOnly = false;
            this._observer = this.scene.getEngine().onEndFrameObservable.add(this._handle)!;
            this.scene.getEngine().onEndFrameObservable.makeObserverBottomPriority(this._observer);
        } else {
            this._observer = this.scene.onBeforeRenderObservable.add(this._handle)!;
            this.scene.onBeforeRenderObservable.makeObserverBottomPriority(this._observer);
        }

    }

    /**
     * 禁用冻结行为
     */
    public disable(): void {
        if (this._cameraGravity !== null) {
            (this.camera as FreeCamera).applyGravity = this._cameraGravity;
            this._cameraGravity = null;
        }
        this.scene.getEngine().onEndFrameObservable.remove(this._observer);
        this.scene.onBeforeRenderObservable.remove(this._observer);
    }

    /**
     * 停止冻结并移除活动相机改变事件
     */
    public destroy(): void {
        this.disable();
        if (this._activeCameraChangeHandle) this.scene.onActiveCameraChanged.removeCallback(this._activeCameraChangeHandle);
    }

    /**
     * 当前目标相机是否为第一人称相机
     * @returns 是否第一人称相机
     */
    private isFPSCamera(): boolean {
        const cam = this.camera;
        if (cam instanceof WebXRCamera) return true;
        if (cam instanceof UniversalCamera) return true;
        if (cam instanceof FreeCamera) return true;
        return false;
    }

    /**
     * 活动场景
     */
    public get scene(): Scene {
        if (this._scene) {
            return this._scene;
        } else {
            return this._camera.getScene();
        }
    }

    public set scene(scene: Scene) {
        this._scene = scene;
    }

    /**
     * 活动相机
     */
    public get camera(): TargetCamera {
        if (this._scene) {
            let cam = this._scene.activeCamera as TargetCamera;
            if (cam) return cam;
            cam = this._scene.cameras[0] as TargetCamera;
            if (cam) return cam;
            return new UniversalCamera('freeze-camera-created-camera+' + this._scene.uid, Vector3.ZeroReadOnly, this._scene);
        } else {
            return this._camera;
        }
    }

    public set camera(camera: TargetCamera) {
        this._camera = camera;
    }

}


/**
 * 冻结相机（位置和/或旋转，仅支持WebXRCamera、UniversalCamera、FreeCamera）
 * todo:freezing of camera rotation has not been achieved
 */
class FreezeCamera {

    private static _infos: FreezeCameraHandleInfo[] = [];
    private static _tv0 = new Vector3();
    private static _tv1 = new Vector3();
    private static _tq0 = new Quaternion();

    /**
     * 相机是否正被冻结
     * @param cameraOrScene 目标相机或场景
     * @returns 是否冻结中
     */
    public static freezing(cameraOrScene: TargetCamera | Scene): boolean {
        if (cameraOrScene instanceof Scene) {
            for (const info of this._infos) {
                if (info.scene.uid === cameraOrScene.uid) return true;
            }
        } else {
            for (const info of this._infos) {
                if (info.camera.id === cameraOrScene.id) return true;
            }
        }
        return false;
    }

    /**
     * 根据id获取冻结处理实例
     * @param id 实例id
     * @returns 冻结处理实例
     */
    public static getHandleById(id: number): FreezeCameraHandleInfo | null {
        for (const hd of this._infos) {
            if (hd.id === id) return hd;
        }
        return null;
    }

    /**
     * 根据相机或场景获取冻结处理实例
     * @param cameraOrScene 相机或场景
     * @returns 冻结处理实例
     */
    public static getHandleByCameraOrScene(cameraOrScene: TargetCamera | Scene): FreezeCameraHandleInfo | null {
        if (cameraOrScene instanceof Scene) {
            for (const hd of this._infos) {
                if (cameraOrScene.uid === hd.scene.uid) return hd;
            }
        } else {
            for (const hd of this._infos) {
                if (cameraOrScene.id === hd.camera.id) return hd;
            }
        }
        return null;
    }

    /**
     * 根据相机或场景id获取冻结处理实例
     * @param cameraOrSceneId 相机或场景id
     * @returns 冻结处理实例
     */
    public static getHandleByCameraOrSceneId(cameraOrSceneId: string): FreezeCameraHandleInfo | null {
        for (const hd of this._infos) {
            if (cameraOrSceneId === hd.camera.id || cameraOrSceneId === hd.scene.uid) return hd;
        }
        return null;
    }

    /**
     * 冻结位置和旋转
     * @param cameraOrScene 目标
     * @param target 参考对象
     * @returns 生成的冻结信息id
     */
    public static freezeAll(cameraOrScene: TargetCamera | Scene, target?: TransformNode): number {
        return this.freeze(cameraOrScene, zs.freezeAllHandle, target);
    }

    /**
     * 冻结位置及水平旋转
     * @param cameraOrScene 目标
     * @param target 参考对象
     * @returns 生成的冻结信息id
     */
    public static freezePositionHorizontalRotation(cameraOrScene: TargetCamera | Scene, target?: TransformNode): number {
        return this.freeze(cameraOrScene, FreezeCamera.freezePositionAndHorizontalRotationHandle, target);
    }

    /**
     * 冻结位置（世界）
     * @param cameraOrScene 目标
     * @param target 参考对象
     * @returns 生成的冻结信息id
     */
    public static freezePosition(cameraOrScene: TargetCamera | Scene, target?: TransformNode): number {
        return this.freeze(cameraOrScene, FreezeCamera.freezePositionHandle, target);
    }

    /**
     * 冻结位置（局部）
     * @param cameraOrScene 目标
     * @param target 参考对象
     * @returns 生成的冻结信息id
     */
    public static freezeLocalPosition(cameraOrScene: TargetCamera | Scene, target?: TransformNode): number {
        return this.freeze(cameraOrScene, FreezeCamera.freezeLocalPositionHandle, target);
    }

    /**
     * 取消冻结
     * @param handleInfoIdOrCameraOrScene 冻结信息id或目标
     */
    public static unfreeze(handleInfoIdOrCameraOrScene?: number | TargetCamera | Scene): void {
        if (handleInfoIdOrCameraOrScene === undefined) {
            for (const info of this._infos) {
                info.destroy();
            }
            this._infos.length = 0;
            return;
        }
        if (typeof handleInfoIdOrCameraOrScene === 'number') {
            const handleInfoId = handleInfoIdOrCameraOrScene;
            for (let i = 0; i < this._infos.length; i++) {
                const info = this._infos[i];
                if (info.id === handleInfoId) {
                    info.destroy();
                    this._infos.splice(i, 1);
                    return;
                }
            }
        } else if (handleInfoIdOrCameraOrScene instanceof TargetCamera) {
            const cam = handleInfoIdOrCameraOrScene;
            for (let i = 0; i < this._infos.length; i++) {
                const info = this._infos[i];
                if (info.camera.id === cam.id) {
                    info.destroy();
                    this._infos.splice(i, 1);
                    i--;
                }
            }
        } else {
            const scn = handleInfoIdOrCameraOrScene;
            for (let i = 0; i < this._infos.length; i++) {
                const info = this._infos[i];
                if (info.scene.uid === scn.uid) {
                    info.destroy();
                    this._infos.splice(i, 1);
                    i--;
                }
            }
        }
    }

    /**
     * 生成冻结信息
     * @param cameraOrScene 目标
     * @param callback 回调函数
     * @param target 参考对象
     * @returns 冻结信息id
     */
    private static freeze(cameraOrScene: TargetCamera | Scene, callback: IHandleCallback, target?: TransformNode): number {
        this.unfreeze(cameraOrScene);
        const hi = new FreezeCameraHandleInfo(cameraOrScene, target);
        hi.callback = callback;
        hi.enable();
        this._infos.push(hi);
        return hi.id;
    }

    /**
     * 设置目标相机位置
     * @param camera 目标相机
     * @param position 位置
     */
    private static setCameraPosition(camera: TargetCamera, position: Vector3): void {
        if (camera instanceof ArcRotateCamera) {
            camera.position = position;
        } else {
            camera.position.copyFrom(position);
        }
    }

    /**
     * 冻结位置的处理函数（世界）
     * @param camera 目标相机
     * @param absolutePosition 世界位置
     * @param absoluteRotation 世界旋转
     * @param position 位置
     * @param rotation 旋转
     */
    private static freezePositionHandle(camera: TargetCamera, absolutePosition: Vector3, absoluteRotation: Vector3 | Quaternion, position: Vector3, rotation: Vector3 | Quaternion): void {
        FreezeCamera.setCameraPosition(camera, absolutePosition);
    }

    /**
     * 冻结位置的处理函数（局部）
     * @param camera 目标相机
     * @param absolutePosition 世界位置
     * @param absoluteRotation 世界旋转
     * @param position 位置
     * @param rotation 旋转
     */
    private static freezeLocalPositionHandle(camera: TargetCamera, absolutePosition: Vector3, absoluteRotation: Vector3 | Quaternion, position: Vector3, rotation: Vector3 | Quaternion): void {
        FreezeCamera.setCameraPosition(camera, position);
    }

    /**
     * 冻结位置及旋转的处理函数
     * @param camera 目标相机
     * @param absolutePosition 世界位置
     * @param absoluteRotation 世界旋转
     * @param position 位置
     * @param rotation 旋转
     */
    private static freezeAllHandle(camera: TargetCamera, absolutePosition: Vector3, absoluteRotation: Vector3 | Quaternion, position: Vector3, rotation: Vector3 | Quaternion): void {
        FreezeCamera.setCameraPosition(camera, absolutePosition);
        if (absoluteRotation instanceof Quaternion) {
            camera.rotationQuaternion.copyFrom(absoluteRotation);
        } else {
            camera.rotation.copyFrom(absoluteRotation);
        }
    }

    /**
     * 冻结位置及水平旋转的处理函数
     * @param camera 目标相机
     * @param absolutePosition 世界位置
     * @param absoluteRotation 世界旋转
     * @param position 位置
     * @param rotation 旋转
     */
    private static freezePositionAndHorizontalRotationHandle(camera: TargetCamera, absolutePosition: Vector3, absoluteRotation: Vector3 | Quaternion, position: Vector3, rotation: Vector3 | Quaternion): void {
        FreezeCamera.setCameraPosition(camera, position);
        if (rotation instanceof Quaternion) {
            const rt = rotation.toEulerAnglesToRef(FreezeCamera._tv0);
            const rc = camera.rotationQuaternion.toEulerAnglesToRef(FreezeCamera._tv1);
            const q = Quaternion.FromEulerAnglesToRef(rc.x, rt.y, rc.z, FreezeCamera._tq0);
            camera.rotationQuaternion.copyFrom(q);
        } else {
            camera.rotation.y = rotation.y;
        }
    }

}
const zs = FreezeCamera;


export { FreezeCamera, FreezeCameraHandleInfo };